Skip to content

chore: implement phase 9 - #2787

Merged
Tofel merged 3 commits into
dx-5122-alerts-assertion-p8from
dx-5122-alerts-assertion-p9
Sep 9, 2026
Merged

chore: implement phase 9#2787
Tofel merged 3 commits into
dx-5122-alerts-assertion-p8from
dx-5122-alerts-assertion-p9

Conversation

@Tofel

@Tofel Tofel commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Implements Check(), the I/O shell around pure decide() (invariants H5/H7). Single-step mode synthesizes the header and its own sentinel, so no mode flag reaches the pure layer; log mode stops the recorder before the one full read. The header (not a re-resolved definition) decides skipped, the drain set, and transitionGrace; the flock (not the pidfile) is the authority for whether a writer still exists.

Review focus: check.go mode handling, and the header-vs-flock authority decisions.

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

⚠️ API Diff Results - github.com/smartcontractkit/chainlink-testing-framework/grafana-alertcheck

⚠️ Breaking Changes (1)

package github (1)
  • com/smartcontractkit/chainlink-testing-framework/grafana-alertcheck/cmd/grafana-alertcheck — 🗑️ Removed

✅ Compatible Changes (1)

package github (1)
  • com/smartcontractkit/chainlink-testing-framework/grafana-alertcheck/cmd — ➕ Added

📄 View full apidiff report

@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p8 branch from 83cf391 to dc4fadf Compare September 1, 2026 11:25
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p9 branch 2 times, most recently from 99e5355 to 95381e3 Compare September 1, 2026 14:18
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p8 branch 2 times, most recently from ee34ac5 to 686ccc2 Compare September 1, 2026 14:46
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p9 branch from 95381e3 to 04ba08e Compare September 1, 2026 14:46
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p8 branch from 686ccc2 to cf2505e Compare September 1, 2026 15:25
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p9 branch 2 times, most recently from 267665d to 7d9cc1e Compare September 2, 2026 09:45
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p8 branch from cf2505e to 13df605 Compare September 2, 2026 09:45
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p9 branch from 7d9cc1e to effe464 Compare September 2, 2026 09:57
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p8 branch from 13df605 to 97a296f Compare September 2, 2026 09:57
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p9 branch from effe464 to ce02d17 Compare September 2, 2026 11:05
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p8 branch from fb3ed03 to 46afcc1 Compare September 4, 2026 15:03
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p9 branch 2 times, most recently from 1377467 to f659144 Compare September 4, 2026 15:15
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p8 branch 2 times, most recently from 33cec3e to 55f5288 Compare September 7, 2026 09:35
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p9 branch from f659144 to c61028a Compare September 7, 2026 09:35
@Tofel
Tofel marked this pull request as ready for review September 7, 2026 09:37
@Tofel
Tofel requested a review from a team as a code owner September 7, 2026 09:37
Copilot AI lite review requested due to automatic review settings September 7, 2026 09:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

It introduces a large, correctness-critical orchestration layer (check.go) with substantial new behavior despite strong tests, so final human review is warranted.

Pull request overview

Implements Phase 9 by adding the Check() I/O shell around the existing pure classification/coverage logic, with explicit handling for single-step vs recorded-log modes and tightened “authority” decisions (header vs re-resolved definitions; flock vs pidfile) to avoid fail-open behavior.

Changes:

  • Added Check() (and helpers) to orchestrate validation, definition resolution, timing derivation, evidence collection, recorder shutdown via flock authority, drain wait, and final decide() classification.
  • Centralized first-round polling into firstObservations() for both watch-start and single-step measurement passes, including §3.2 verification and stable poll ordering.
  • Updated timing/skipped logic so “paused at window start” is sourced from the log header (or equivalent start-of-step snapshot) rather than post-window re-resolved definitions; added supporting helpers and tests.
File summaries
File Description
grafana-alertcheck/internal/gate/watch.go Extracts firstObservations() and uses it for initial heartbeats/latency measurement prior to budget checking.
grafana-alertcheck/internal/gate/watch_daemon_test.go Adds a lock-holder test helper process to validate flock-based recorder stop behavior.
grafana-alertcheck/internal/gate/schedule.go Makes transitionGrace exclusion depend on “paused at start” authority (header/snapshot), not post-window definition state.
grafana-alertcheck/internal/gate/schedule_test.go Pins the new header-authoritative grace exclusion behavior in log mode.
grafana-alertcheck/internal/gate/log.go Adds Header.pausedAtStart(), stateRuleByUID(), and ReadLogHeader() to support early identity checks and shared UID selection logic.
grafana-alertcheck/internal/gate/flock.go Adds tryLockExclusive() to distinguish contention from actual flock failures for “writer exists?” probing.
grafana-alertcheck/internal/gate/coverage.go Updates commentary to reflect Phase 9/P8 responsibilities now implemented elsewhere.
grafana-alertcheck/internal/gate/classify.go Switches skipped-rule detection to header authority via pausedAtStart.
grafana-alertcheck/internal/gate/classify_test.go Updates tests to construct pause authority via header rather than Definition.IsPaused.
grafana-alertcheck/internal/gate/check.go New: full Phase 9 Check() implementation including validation, early header read, stop protocol, drain wait, and result merge.
grafana-alertcheck/internal/gate/check_test.go New: comprehensive integration-style tests for single-step and recorder modes, including edge cases and regressions.
grafana-alertcheck/internal/gate/check_process.go New: process signaling helper (signalRecorder) used by recorder stop logic.
Review details
  • Files reviewed: 12/12 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread grafana-alertcheck/internal/gate/watch.go Outdated
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p8 branch from 55f5288 to e79d3c1 Compare September 7, 2026 09:46
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p9 branch from c61028a to ab74eba Compare September 7, 2026 09:46
Invariant defended: H5/H7. The one question: can check report a pass
over a window it did not prove?

Check() is the I/O shell around the pure decide(). Single-step
synthesizes the header and its own sentinel, so no mode flag reaches
the pure layer. Log mode stops the recorder before the one full read.

The header, not a definition re-resolved after the window closed, is
the authority for what was paused when the window opened — it decides
`skipped`, the drain set, and the transitionGrace max. The flock, not
the pidfile, is the authority for whether a writer still exists.
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p9 branch from ab74eba to 7c3f206 Compare September 7, 2026 15:02
@Tofel
Tofel force-pushed the dx-5122-alerts-assertion-p8 branch from e79d3c1 to 48393bc Compare September 7, 2026 15:02
@Tofel
Tofel removed this pull request from stack #2791 September 9, 2026 09:52
@Tofel Tofel closed this Sep 9, 2026
* Wire watch/check subcommands to the gate library, with a table+JSON
renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global
thresholds and a real skew bound; export SkewHardLimit; reject --states
normal.

* chore: fix goreleaser.yaml and add version command

* chore: implement phase 11 (#2789)

* chore: implement phase 11

Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state,
not just an observed reason) and close the remaining §22 gaps: newly_bad's
no-early-exit clock assertion, a recorder-mode gap right after the deploy,
a rule's own coverage gap overriding its own recovery, a genuinely
skew-discriminating staleness test, exit-2 consequences on two
Reason-only coverage tests, and end-to-end checks for log-name
collapse, a truncated log, and the real watch-written state histogram.

* chore: address code review comments

* chore: more concise comments (#2790)

* chore: more concise comments

* fix: merge conflict

* chore: shorten comments

* fix: resolve conflict

* chore: use testify's require in tests (#2792)

* chore: use testify's require in tests

* chore: move remaining assumptions to testify

* chore: address code review comments

* chore: fix logging and std out printing (#2798)

* chore: fix logging and std out printing

* chore: truncate to seconds when comparing from time

* chore: add centralized docs (#2802)

* chore: add centralized docs

* chore: further update docs

* chore: address code review comments (#2807)

* chore: address code review comments

* chore: get rid of goreleaser
@Tofel Tofel reopened this Sep 9, 2026
@Tofel
Tofel merged commit 3bc0054 into dx-5122-alerts-assertion-p8 Sep 9, 2026
57 of 60 checks passed
@Tofel
Tofel deleted the dx-5122-alerts-assertion-p9 branch September 9, 2026 09:58
Tofel added a commit that referenced this pull request Sep 9, 2026
* chore: implement phase 8

Invariant defended: H6/H7. The one question: can a violation ever
outrank an unobservable rule, or can a pass happen without
Violations empty and err nil?

Adds classify.go: the pure per-instance classifier (outcome table,
preexisting policy, BadFor) and decide(), the seam combining
proveCoverage with those timelines under one Policy. Consolidates
rule-poll filtering and skew translation onto pollsForRule/runnerTime,
shared with coverage.go.

* chore: rename some vars + add unit tests

* chore: address code review comments

* chore: implement phase 9 (#2787)

* chore: implement phase 9

Invariant defended: H5/H7. The one question: can check report a pass
over a window it did not prove?

Check() is the I/O shell around the pure decide(). Single-step
synthesizes the header and its own sentinel, so no mode flag reaches
the pure layer. Log mode stops the recorder before the one full read.

The header, not a definition re-resolved after the window closed, is
the authority for what was paused when the window opened — it decides
`skipped`, the drain set, and the transitionGrace max. The flock, not
the pidfile, is the authority for whether a writer still exists.

* chore: remove unix build tag

* chore: implement phase 10 (#2788)

* Wire watch/check subcommands to the gate library, with a table+JSON
renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global
thresholds and a real skew bound; export SkewHardLimit; reject --states
normal.

* chore: fix goreleaser.yaml and add version command

* chore: implement phase 11 (#2789)

* chore: implement phase 11

Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state,
not just an observed reason) and close the remaining §22 gaps: newly_bad's
no-early-exit clock assertion, a recorder-mode gap right after the deploy,
a rule's own coverage gap overriding its own recovery, a genuinely
skew-discriminating staleness test, exit-2 consequences on two
Reason-only coverage tests, and end-to-end checks for log-name
collapse, a truncated log, and the real watch-written state histogram.

* chore: address code review comments

* chore: more concise comments (#2790)

* chore: more concise comments

* fix: merge conflict

* chore: shorten comments

* fix: resolve conflict

* chore: use testify's require in tests (#2792)

* chore: use testify's require in tests

* chore: move remaining assumptions to testify

* chore: address code review comments

* chore: fix logging and std out printing (#2798)

* chore: fix logging and std out printing

* chore: truncate to seconds when comparing from time

* chore: add centralized docs (#2802)

* chore: add centralized docs

* chore: further update docs

* chore: address code review comments (#2807)

* chore: address code review comments

* chore: get rid of goreleaser
Tofel added a commit that referenced this pull request Sep 9, 2026
* chore: implement phase 7

Invariant defended: H3. The one question: can a rule be called alive
because it looked alive one poll ago?

proveCoverage (grafana-alertcheck/internal/gate/coverage.go) is the pure
coverage function: nine checks over one rule's polls — sentinel,
from-bounds, heartbeat continuity, health error/nodata, liveness,
in-window pause, rule absence, KeepLast. Liveness is absolute, never a
delta. Cross-domain comparisons translate by each poll's own skew and
widen boundary segments by its skew bound, fail-closed.

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 8 (#2786)

* chore: implement phase 8

Invariant defended: H6/H7. The one question: can a violation ever
outrank an unobservable rule, or can a pass happen without
Violations empty and err nil?

Adds classify.go: the pure per-instance classifier (outcome table,
preexisting policy, BadFor) and decide(), the seam combining
proveCoverage with those timelines under one Policy. Consolidates
rule-poll filtering and skew translation onto pollsForRule/runnerTime,
shared with coverage.go.

* chore: rename some vars + add unit tests

* chore: address code review comments

* chore: implement phase 9 (#2787)

* chore: implement phase 9

Invariant defended: H5/H7. The one question: can check report a pass
over a window it did not prove?

Check() is the I/O shell around the pure decide(). Single-step
synthesizes the header and its own sentinel, so no mode flag reaches
the pure layer. Log mode stops the recorder before the one full read.

The header, not a definition re-resolved after the window closed, is
the authority for what was paused when the window opened — it decides
`skipped`, the drain set, and the transitionGrace max. The flock, not
the pidfile, is the authority for whether a writer still exists.

* chore: remove unix build tag

* chore: implement phase 10 (#2788)

* Wire watch/check subcommands to the gate library, with a table+JSON
renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global
thresholds and a real skew bound; export SkewHardLimit; reject --states
normal.

* chore: fix goreleaser.yaml and add version command

* chore: implement phase 11 (#2789)

* chore: implement phase 11

Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state,
not just an observed reason) and close the remaining §22 gaps: newly_bad's
no-early-exit clock assertion, a recorder-mode gap right after the deploy,
a rule's own coverage gap overriding its own recovery, a genuinely
skew-discriminating staleness test, exit-2 consequences on two
Reason-only coverage tests, and end-to-end checks for log-name
collapse, a truncated log, and the real watch-written state histogram.

* chore: address code review comments

* chore: more concise comments (#2790)

* chore: more concise comments

* fix: merge conflict

* chore: shorten comments

* fix: resolve conflict

* chore: use testify's require in tests (#2792)

* chore: use testify's require in tests

* chore: move remaining assumptions to testify

* chore: address code review comments

* chore: fix logging and std out printing (#2798)

* chore: fix logging and std out printing

* chore: truncate to seconds when comparing from time

* chore: add centralized docs (#2802)

* chore: add centralized docs

* chore: further update docs

* chore: address code review comments (#2807)

* chore: address code review comments

* chore: get rid of goreleaser
Tofel added a commit that referenced this pull request Sep 9, 2026
* chore: implement phase 6

Invariant defended: H2. The one question: can watch return success over a
window that nothing is recording?

Watch() records the first observation of each non-skipped rule, then detaches
a child that polls at the cadence in the header. The parent returns only after
the child reports ready on an inherited pipe, and writes the pidfile after
that. A clean stop writes the sentinel; a hard error does not.

* chore: add a unit test, remove build tags

* chore: address code review comments

* chore: implement phase 7 (#2785)

* chore: implement phase 7

Invariant defended: H3. The one question: can a rule be called alive
because it looked alive one poll ago?

proveCoverage (grafana-alertcheck/internal/gate/coverage.go) is the pure
coverage function: nine checks over one rule's polls — sentinel,
from-bounds, heartbeat continuity, health error/nodata, liveness,
in-window pause, rule absence, KeepLast. Liveness is absolute, never a
delta. Cross-domain comparisons translate by each poll's own skew and
widen boundary segments by its skew bound, fail-closed.

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 8 (#2786)

* chore: implement phase 8

Invariant defended: H6/H7. The one question: can a violation ever
outrank an unobservable rule, or can a pass happen without
Violations empty and err nil?

Adds classify.go: the pure per-instance classifier (outcome table,
preexisting policy, BadFor) and decide(), the seam combining
proveCoverage with those timelines under one Policy. Consolidates
rule-poll filtering and skew translation onto pollsForRule/runnerTime,
shared with coverage.go.

* chore: rename some vars + add unit tests

* chore: address code review comments

* chore: implement phase 9 (#2787)

* chore: implement phase 9

Invariant defended: H5/H7. The one question: can check report a pass
over a window it did not prove?

Check() is the I/O shell around the pure decide(). Single-step
synthesizes the header and its own sentinel, so no mode flag reaches
the pure layer. Log mode stops the recorder before the one full read.

The header, not a definition re-resolved after the window closed, is
the authority for what was paused when the window opened — it decides
`skipped`, the drain set, and the transitionGrace max. The flock, not
the pidfile, is the authority for whether a writer still exists.

* chore: remove unix build tag

* chore: implement phase 10 (#2788)

* Wire watch/check subcommands to the gate library, with a table+JSON
renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global
thresholds and a real skew bound; export SkewHardLimit; reject --states
normal.

* chore: fix goreleaser.yaml and add version command

* chore: implement phase 11 (#2789)

* chore: implement phase 11

Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state,
not just an observed reason) and close the remaining §22 gaps: newly_bad's
no-early-exit clock assertion, a recorder-mode gap right after the deploy,
a rule's own coverage gap overriding its own recovery, a genuinely
skew-discriminating staleness test, exit-2 consequences on two
Reason-only coverage tests, and end-to-end checks for log-name
collapse, a truncated log, and the real watch-written state histogram.

* chore: address code review comments

* chore: more concise comments (#2790)

* chore: more concise comments

* fix: merge conflict

* chore: shorten comments

* fix: resolve conflict

* chore: use testify's require in tests (#2792)

* chore: use testify's require in tests

* chore: move remaining assumptions to testify

* chore: address code review comments

* chore: fix logging and std out printing (#2798)

* chore: fix logging and std out printing

* chore: truncate to seconds when comparing from time

* chore: add centralized docs (#2802)

* chore: add centralized docs

* chore: further update docs

* chore: address code review comments (#2807)

* chore: address code review comments

* chore: get rid of goreleaser
Tofel added a commit that referenced this pull request Sep 9, 2026
* chore: implement phase 5

Add the JSONL evidence log (P5).

- log.go: Header/Poll records, reduction, H2 transition markers,
  §3.2 verification, append-only Writer with flock, ReadLog
- flock_unix.go: non-blocking exclusive lock, unix only
- schedule.go: DeriveTimingsFromLog — log-mode cadence comes from the
  header, never from the definitions

* chore: remove unix build tags

* chore: address code review comments

* chore: implement phase 6 (#2784)

* chore: implement phase 6

Invariant defended: H2. The one question: can watch return success over a
window that nothing is recording?

Watch() records the first observation of each non-skipped rule, then detaches
a child that polls at the cadence in the header. The parent returns only after
the child reports ready on an inherited pipe, and writes the pidfile after
that. A clean stop writes the sentinel; a hard error does not.

* chore: add a unit test, remove build tags

* chore: address code review comments

* chore: implement phase 7 (#2785)

* chore: implement phase 7

Invariant defended: H3. The one question: can a rule be called alive
because it looked alive one poll ago?

proveCoverage (grafana-alertcheck/internal/gate/coverage.go) is the pure
coverage function: nine checks over one rule's polls — sentinel,
from-bounds, heartbeat continuity, health error/nodata, liveness,
in-window pause, rule absence, KeepLast. Liveness is absolute, never a
delta. Cross-domain comparisons translate by each poll's own skew and
widen boundary segments by its skew bound, fail-closed.

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 8 (#2786)

* chore: implement phase 8

Invariant defended: H6/H7. The one question: can a violation ever
outrank an unobservable rule, or can a pass happen without
Violations empty and err nil?

Adds classify.go: the pure per-instance classifier (outcome table,
preexisting policy, BadFor) and decide(), the seam combining
proveCoverage with those timelines under one Policy. Consolidates
rule-poll filtering and skew translation onto pollsForRule/runnerTime,
shared with coverage.go.

* chore: rename some vars + add unit tests

* chore: address code review comments

* chore: implement phase 9 (#2787)

* chore: implement phase 9

Invariant defended: H5/H7. The one question: can check report a pass
over a window it did not prove?

Check() is the I/O shell around the pure decide(). Single-step
synthesizes the header and its own sentinel, so no mode flag reaches
the pure layer. Log mode stops the recorder before the one full read.

The header, not a definition re-resolved after the window closed, is
the authority for what was paused when the window opened — it decides
`skipped`, the drain set, and the transitionGrace max. The flock, not
the pidfile, is the authority for whether a writer still exists.

* chore: remove unix build tag

* chore: implement phase 10 (#2788)

* Wire watch/check subcommands to the gate library, with a table+JSON
renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global
thresholds and a real skew bound; export SkewHardLimit; reject --states
normal.

* chore: fix goreleaser.yaml and add version command

* chore: implement phase 11 (#2789)

* chore: implement phase 11

Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state,
not just an observed reason) and close the remaining §22 gaps: newly_bad's
no-early-exit clock assertion, a recorder-mode gap right after the deploy,
a rule's own coverage gap overriding its own recovery, a genuinely
skew-discriminating staleness test, exit-2 consequences on two
Reason-only coverage tests, and end-to-end checks for log-name
collapse, a truncated log, and the real watch-written state histogram.

* chore: address code review comments

* chore: more concise comments (#2790)

* chore: more concise comments

* fix: merge conflict

* chore: shorten comments

* fix: resolve conflict

* chore: use testify's require in tests (#2792)

* chore: use testify's require in tests

* chore: move remaining assumptions to testify

* chore: address code review comments

* chore: fix logging and std out printing (#2798)

* chore: fix logging and std out printing

* chore: truncate to seconds when comparing from time

* chore: add centralized docs (#2802)

* chore: add centralized docs

* chore: further update docs

* chore: address code review comments (#2807)

* chore: address code review comments

* chore: get rid of goreleaser
Tofel added a commit that referenced this pull request Sep 9, 2026
* chore: implement phase 4

Add per-rule poll timings, scheduler, and budget check (P4).

- schedule.go: DeriveTimings, Scheduler, CheckBudget (§5)
- Address review: add Folder/Title resolve test, rename
  CheckBudget's minPollEvery to tightestUID

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 5 (#2783)

* chore: implement phase 5

Add the JSONL evidence log (P5).

- log.go: Header/Poll records, reduction, H2 transition markers,
  §3.2 verification, append-only Writer with flock, ReadLog
- flock_unix.go: non-blocking exclusive lock, unix only
- schedule.go: DeriveTimingsFromLog — log-mode cadence comes from the
  header, never from the definitions

* chore: remove unix build tags

* chore: address code review comments

* chore: implement phase 6 (#2784)

* chore: implement phase 6

Invariant defended: H2. The one question: can watch return success over a
window that nothing is recording?

Watch() records the first observation of each non-skipped rule, then detaches
a child that polls at the cadence in the header. The parent returns only after
the child reports ready on an inherited pipe, and writes the pidfile after
that. A clean stop writes the sentinel; a hard error does not.

* chore: add a unit test, remove build tags

* chore: address code review comments

* chore: implement phase 7 (#2785)

* chore: implement phase 7

Invariant defended: H3. The one question: can a rule be called alive
because it looked alive one poll ago?

proveCoverage (grafana-alertcheck/internal/gate/coverage.go) is the pure
coverage function: nine checks over one rule's polls — sentinel,
from-bounds, heartbeat continuity, health error/nodata, liveness,
in-window pause, rule absence, KeepLast. Liveness is absolute, never a
delta. Cross-domain comparisons translate by each poll's own skew and
widen boundary segments by its skew bound, fail-closed.

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 8 (#2786)

* chore: implement phase 8

Invariant defended: H6/H7. The one question: can a violation ever
outrank an unobservable rule, or can a pass happen without
Violations empty and err nil?

Adds classify.go: the pure per-instance classifier (outcome table,
preexisting policy, BadFor) and decide(), the seam combining
proveCoverage with those timelines under one Policy. Consolidates
rule-poll filtering and skew translation onto pollsForRule/runnerTime,
shared with coverage.go.

* chore: rename some vars + add unit tests

* chore: address code review comments

* chore: implement phase 9 (#2787)

* chore: implement phase 9

Invariant defended: H5/H7. The one question: can check report a pass
over a window it did not prove?

Check() is the I/O shell around the pure decide(). Single-step
synthesizes the header and its own sentinel, so no mode flag reaches
the pure layer. Log mode stops the recorder before the one full read.

The header, not a definition re-resolved after the window closed, is
the authority for what was paused when the window opened — it decides
`skipped`, the drain set, and the transitionGrace max. The flock, not
the pidfile, is the authority for whether a writer still exists.

* chore: remove unix build tag

* chore: implement phase 10 (#2788)

* Wire watch/check subcommands to the gate library, with a table+JSON
renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global
thresholds and a real skew bound; export SkewHardLimit; reject --states
normal.

* chore: fix goreleaser.yaml and add version command

* chore: implement phase 11 (#2789)

* chore: implement phase 11

Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state,
not just an observed reason) and close the remaining §22 gaps: newly_bad's
no-early-exit clock assertion, a recorder-mode gap right after the deploy,
a rule's own coverage gap overriding its own recovery, a genuinely
skew-discriminating staleness test, exit-2 consequences on two
Reason-only coverage tests, and end-to-end checks for log-name
collapse, a truncated log, and the real watch-written state histogram.

* chore: address code review comments

* chore: more concise comments (#2790)

* chore: more concise comments

* fix: merge conflict

* chore: shorten comments

* fix: resolve conflict

* chore: use testify's require in tests (#2792)

* chore: use testify's require in tests

* chore: move remaining assumptions to testify

* chore: address code review comments

* chore: fix logging and std out printing (#2798)

* chore: fix logging and std out printing

* chore: truncate to seconds when comparing from time

* chore: add centralized docs (#2802)

* chore: add centralized docs

* chore: further update docs

* chore: address code review comments (#2807)

* chore: address code review comments

* chore: get rid of goreleaser
Tofel added a commit that referenced this pull request Sep 9, 2026
* chore: implement phase 3

Add Resolve() for alert name resolution (uid:/Title/Folder/Title/
Folder/Group/Title forms, UID collapse, no-match suggestions) and the
grafana-alertcheck CLI's list subcommand, the first runnable piece of
the gate.

Incorporates review fixes: reject empty path segments in classifyForm,
guard uid: against an empty suffix, scope the no-match rule count and
suggestions to supported rule kinds only, and exit 0 on -h/--help.

* chore: enhance unit tests

* chore: implement phase 4 (#2782)

* chore: implement phase 4

Add per-rule poll timings, scheduler, and budget check (P4).

- schedule.go: DeriveTimings, Scheduler, CheckBudget (§5)
- Address review: add Folder/Title resolve test, rename
  CheckBudget's minPollEvery to tightestUID

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 5 (#2783)

* chore: implement phase 5

Add the JSONL evidence log (P5).

- log.go: Header/Poll records, reduction, H2 transition markers,
  §3.2 verification, append-only Writer with flock, ReadLog
- flock_unix.go: non-blocking exclusive lock, unix only
- schedule.go: DeriveTimingsFromLog — log-mode cadence comes from the
  header, never from the definitions

* chore: remove unix build tags

* chore: address code review comments

* chore: implement phase 6 (#2784)

* chore: implement phase 6

Invariant defended: H2. The one question: can watch return success over a
window that nothing is recording?

Watch() records the first observation of each non-skipped rule, then detaches
a child that polls at the cadence in the header. The parent returns only after
the child reports ready on an inherited pipe, and writes the pidfile after
that. A clean stop writes the sentinel; a hard error does not.

* chore: add a unit test, remove build tags

* chore: address code review comments

* chore: implement phase 7 (#2785)

* chore: implement phase 7

Invariant defended: H3. The one question: can a rule be called alive
because it looked alive one poll ago?

proveCoverage (grafana-alertcheck/internal/gate/coverage.go) is the pure
coverage function: nine checks over one rule's polls — sentinel,
from-bounds, heartbeat continuity, health error/nodata, liveness,
in-window pause, rule absence, KeepLast. Liveness is absolute, never a
delta. Cross-domain comparisons translate by each poll's own skew and
widen boundary segments by its skew bound, fail-closed.

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 8 (#2786)

* chore: implement phase 8

Invariant defended: H6/H7. The one question: can a violation ever
outrank an unobservable rule, or can a pass happen without
Violations empty and err nil?

Adds classify.go: the pure per-instance classifier (outcome table,
preexisting policy, BadFor) and decide(), the seam combining
proveCoverage with those timelines under one Policy. Consolidates
rule-poll filtering and skew translation onto pollsForRule/runnerTime,
shared with coverage.go.

* chore: rename some vars + add unit tests

* chore: address code review comments

* chore: implement phase 9 (#2787)

* chore: implement phase 9

Invariant defended: H5/H7. The one question: can check report a pass
over a window it did not prove?

Check() is the I/O shell around the pure decide(). Single-step
synthesizes the header and its own sentinel, so no mode flag reaches
the pure layer. Log mode stops the recorder before the one full read.

The header, not a definition re-resolved after the window closed, is
the authority for what was paused when the window opened — it decides
`skipped`, the drain set, and the transitionGrace max. The flock, not
the pidfile, is the authority for whether a writer still exists.

* chore: remove unix build tag

* chore: implement phase 10 (#2788)

* Wire watch/check subcommands to the gate library, with a table+JSON
renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global
thresholds and a real skew bound; export SkewHardLimit; reject --states
normal.

* chore: fix goreleaser.yaml and add version command

* chore: implement phase 11 (#2789)

* chore: implement phase 11

Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state,
not just an observed reason) and close the remaining §22 gaps: newly_bad's
no-early-exit clock assertion, a recorder-mode gap right after the deploy,
a rule's own coverage gap overriding its own recovery, a genuinely
skew-discriminating staleness test, exit-2 consequences on two
Reason-only coverage tests, and end-to-end checks for log-name
collapse, a truncated log, and the real watch-written state histogram.

* chore: address code review comments

* chore: more concise comments (#2790)

* chore: more concise comments

* fix: merge conflict

* chore: shorten comments

* fix: resolve conflict

* chore: use testify's require in tests (#2792)

* chore: use testify's require in tests

* chore: move remaining assumptions to testify

* chore: address code review comments

* chore: fix logging and std out printing (#2798)

* chore: fix logging and std out printing

* chore: truncate to seconds when comparing from time

* chore: add centralized docs (#2802)

* chore: add centralized docs

* chore: further update docs

* chore: address code review comments (#2807)

* chore: address code review comments

* chore: get rid of goreleaser
Tofel added a commit that referenced this pull request Sep 9, 2026
* chore: implement phase 2

Fix retry-error conflation, measure full poll latency, and harden Source test doubles for concurrency.

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 3 (#2781)

* chore: implement phase 3

Add Resolve() for alert name resolution (uid:/Title/Folder/Title/
Folder/Group/Title forms, UID collapse, no-match suggestions) and the
grafana-alertcheck CLI's list subcommand, the first runnable piece of
the gate.

Incorporates review fixes: reject empty path segments in classifyForm,
guard uid: against an empty suffix, scope the no-match rule count and
suggestions to supported rule kinds only, and exit 0 on -h/--help.

* chore: enhance unit tests

* chore: implement phase 4 (#2782)

* chore: implement phase 4

Add per-rule poll timings, scheduler, and budget check (P4).

- schedule.go: DeriveTimings, Scheduler, CheckBudget (§5)
- Address review: add Folder/Title resolve test, rename
  CheckBudget's minPollEvery to tightestUID

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 5 (#2783)

* chore: implement phase 5

Add the JSONL evidence log (P5).

- log.go: Header/Poll records, reduction, H2 transition markers,
  §3.2 verification, append-only Writer with flock, ReadLog
- flock_unix.go: non-blocking exclusive lock, unix only
- schedule.go: DeriveTimingsFromLog — log-mode cadence comes from the
  header, never from the definitions

* chore: remove unix build tags

* chore: address code review comments

* chore: implement phase 6 (#2784)

* chore: implement phase 6

Invariant defended: H2. The one question: can watch return success over a
window that nothing is recording?

Watch() records the first observation of each non-skipped rule, then detaches
a child that polls at the cadence in the header. The parent returns only after
the child reports ready on an inherited pipe, and writes the pidfile after
that. A clean stop writes the sentinel; a hard error does not.

* chore: add a unit test, remove build tags

* chore: address code review comments

* chore: implement phase 7 (#2785)

* chore: implement phase 7

Invariant defended: H3. The one question: can a rule be called alive
because it looked alive one poll ago?

proveCoverage (grafana-alertcheck/internal/gate/coverage.go) is the pure
coverage function: nine checks over one rule's polls — sentinel,
from-bounds, heartbeat continuity, health error/nodata, liveness,
in-window pause, rule absence, KeepLast. Liveness is absolute, never a
delta. Cross-domain comparisons translate by each poll's own skew and
widen boundary segments by its skew bound, fail-closed.

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 8 (#2786)

* chore: implement phase 8

Invariant defended: H6/H7. The one question: can a violation ever
outrank an unobservable rule, or can a pass happen without
Violations empty and err nil?

Adds classify.go: the pure per-instance classifier (outcome table,
preexisting policy, BadFor) and decide(), the seam combining
proveCoverage with those timelines under one Policy. Consolidates
rule-poll filtering and skew translation onto pollsForRule/runnerTime,
shared with coverage.go.

* chore: rename some vars + add unit tests

* chore: address code review comments

* chore: implement phase 9 (#2787)

* chore: implement phase 9

Invariant defended: H5/H7. The one question: can check report a pass
over a window it did not prove?

Check() is the I/O shell around the pure decide(). Single-step
synthesizes the header and its own sentinel, so no mode flag reaches
the pure layer. Log mode stops the recorder before the one full read.

The header, not a definition re-resolved after the window closed, is
the authority for what was paused when the window opened — it decides
`skipped`, the drain set, and the transitionGrace max. The flock, not
the pidfile, is the authority for whether a writer still exists.

* chore: remove unix build tag

* chore: implement phase 10 (#2788)

* Wire watch/check subcommands to the gate library, with a table+JSON
renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global
thresholds and a real skew bound; export SkewHardLimit; reject --states
normal.

* chore: fix goreleaser.yaml and add version command

* chore: implement phase 11 (#2789)

* chore: implement phase 11

Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state,
not just an observed reason) and close the remaining §22 gaps: newly_bad's
no-early-exit clock assertion, a recorder-mode gap right after the deploy,
a rule's own coverage gap overriding its own recovery, a genuinely
skew-discriminating staleness test, exit-2 consequences on two
Reason-only coverage tests, and end-to-end checks for log-name
collapse, a truncated log, and the real watch-written state histogram.

* chore: address code review comments

* chore: more concise comments (#2790)

* chore: more concise comments

* fix: merge conflict

* chore: shorten comments

* fix: resolve conflict

* chore: use testify's require in tests (#2792)

* chore: use testify's require in tests

* chore: move remaining assumptions to testify

* chore: address code review comments

* chore: fix logging and std out printing (#2798)

* chore: fix logging and std out printing

* chore: truncate to seconds when comparing from time

* chore: add centralized docs (#2802)

* chore: add centralized docs

* chore: further update docs

* chore: address code review comments (#2807)

* chore: address code review comments

* chore: get rid of goreleaser
Tofel added a commit that referenced this pull request Sep 9, 2026
* chore: implement phase 1

Strict parsers for the state and ruler endpoints (H1), a Prometheus-style
duration parser, and fixtures sliced from real Grafana 13.1.0 payloads
covering every required/optional-field and must-error case, including the
"Normal (NoData)"/"Normal (Error)" composite reason states found live in the
current fleet capture (not in the original plan's vocabulary).

* chore: apply code review comments

* chore: implement phase 2 (#2780)

* chore: implement phase 2

Fix retry-error conflation, measure full poll latency, and harden Source test doubles for concurrency.

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 3 (#2781)

* chore: implement phase 3

Add Resolve() for alert name resolution (uid:/Title/Folder/Title/
Folder/Group/Title forms, UID collapse, no-match suggestions) and the
grafana-alertcheck CLI's list subcommand, the first runnable piece of
the gate.

Incorporates review fixes: reject empty path segments in classifyForm,
guard uid: against an empty suffix, scope the no-match rule count and
suggestions to supported rule kinds only, and exit 0 on -h/--help.

* chore: enhance unit tests

* chore: implement phase 4 (#2782)

* chore: implement phase 4

Add per-rule poll timings, scheduler, and budget check (P4).

- schedule.go: DeriveTimings, Scheduler, CheckBudget (§5)
- Address review: add Folder/Title resolve test, rename
  CheckBudget's minPollEvery to tightestUID

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 5 (#2783)

* chore: implement phase 5

Add the JSONL evidence log (P5).

- log.go: Header/Poll records, reduction, H2 transition markers,
  §3.2 verification, append-only Writer with flock, ReadLog
- flock_unix.go: non-blocking exclusive lock, unix only
- schedule.go: DeriveTimingsFromLog — log-mode cadence comes from the
  header, never from the definitions

* chore: remove unix build tags

* chore: address code review comments

* chore: implement phase 6 (#2784)

* chore: implement phase 6

Invariant defended: H2. The one question: can watch return success over a
window that nothing is recording?

Watch() records the first observation of each non-skipped rule, then detaches
a child that polls at the cadence in the header. The parent returns only after
the child reports ready on an inherited pipe, and writes the pidfile after
that. A clean stop writes the sentinel; a hard error does not.

* chore: add a unit test, remove build tags

* chore: address code review comments

* chore: implement phase 7 (#2785)

* chore: implement phase 7

Invariant defended: H3. The one question: can a rule be called alive
because it looked alive one poll ago?

proveCoverage (grafana-alertcheck/internal/gate/coverage.go) is the pure
coverage function: nine checks over one rule's polls — sentinel,
from-bounds, heartbeat continuity, health error/nodata, liveness,
in-window pause, rule absence, KeepLast. Liveness is absolute, never a
delta. Cross-domain comparisons translate by each poll's own skew and
widen boundary segments by its skew bound, fail-closed.

* chore: enhance unit tests

* chore: address code review comments

* chore: implement phase 8 (#2786)

* chore: implement phase 8

Invariant defended: H6/H7. The one question: can a violation ever
outrank an unobservable rule, or can a pass happen without
Violations empty and err nil?

Adds classify.go: the pure per-instance classifier (outcome table,
preexisting policy, BadFor) and decide(), the seam combining
proveCoverage with those timelines under one Policy. Consolidates
rule-poll filtering and skew translation onto pollsForRule/runnerTime,
shared with coverage.go.

* chore: rename some vars + add unit tests

* chore: address code review comments

* chore: implement phase 9 (#2787)

* chore: implement phase 9

Invariant defended: H5/H7. The one question: can check report a pass
over a window it did not prove?

Check() is the I/O shell around the pure decide(). Single-step
synthesizes the header and its own sentinel, so no mode flag reaches
the pure layer. Log mode stops the recorder before the one full read.

The header, not a definition re-resolved after the window closed, is
the authority for what was paused when the window opened — it decides
`skipped`, the drain set, and the transitionGrace max. The flock, not
the pidfile, is the authority for whether a writer still exists.

* chore: remove unix build tag

* chore: implement phase 10 (#2788)

* Wire watch/check subcommands to the gate library, with a table+JSON
renderer and H6/H7 exit-code mapping. Extend Result with per-rule/global
thresholds and a real skew bound; export SkewHardLimit; reject --states
normal.

* chore: fix goreleaser.yaml and add version command

* chore: implement phase 11 (#2789)

* chore: implement phase 11

Add coverage.go's declared-KeepLast check (no_data_state/exec_err_state,
not just an observed reason) and close the remaining §22 gaps: newly_bad's
no-early-exit clock assertion, a recorder-mode gap right after the deploy,
a rule's own coverage gap overriding its own recovery, a genuinely
skew-discriminating staleness test, exit-2 consequences on two
Reason-only coverage tests, and end-to-end checks for log-name
collapse, a truncated log, and the real watch-written state histogram.

* chore: address code review comments

* chore: more concise comments (#2790)

* chore: more concise comments

* fix: merge conflict

* chore: shorten comments

* fix: resolve conflict

* chore: use testify's require in tests (#2792)

* chore: use testify's require in tests

* chore: move remaining assumptions to testify

* chore: address code review comments

* chore: fix logging and std out printing (#2798)

* chore: fix logging and std out printing

* chore: truncate to seconds when comparing from time

* chore: add centralized docs (#2802)

* chore: add centralized docs

* chore: further update docs

* chore: address code review comments (#2807)

* chore: address code review comments

* chore: get rid of goreleaser
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants